#include <cerrno>
#include <csignal>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#if __linux
#include <dirent.h>
#include <getopt.h>
#include <ncurses.h>
#include <pthread.h>
#include <search.h>
#include <signal.h>
#include <unistd.h>
#endif

#include "config.h"
#include "gt_display.h"
#include "gt_entry.h"
#include "gt_file.h"
#include "gt_line.h"
#include "gt_option.h"
#include "gt_theme.h"
#include "isearch.h"

// ============== macro ====================
#define CURSOR_UP 'k'
#define CURSOR_DOWN 'j'
#define PAGE_UP 'K'
#define PAGE_DOWN 'J'
#define ENTER 'p'
#define QUIT 'q'
#define MARK 'm'
#define CTRL_D 4
#define CTRL_U 21

#define LOCK(MUTEX)                                                            \
  for (mutex = &MUTEX; mutex && !pthread_mutex_lock(mutex);                    \
       pthread_mutex_unlock(mutex), mutex = 0)
// ============== end macro ==================

// ============== global vars ===============
static search_t *g_search;
static display_t *g_display;
static configuration_t *g_config;
static pthread_t pid;
// =============== end global vars ===========

// ok
void sig_handler(int signo) {
  if (signo == SIGINT) {
    pthread_cancel(pid);
    pthread_join(pid, NULL);
    stop_ncurses(g_display);
    free_search(g_search);
    destroy_config(g_config);
    exit(-1);
  }
}

// ok
static void *lookup_thread(void *arg) {
  DIR *dp;
  search_t *d = (search_t *)arg;
  dp = opendir(d->option->directory);

  if (!dp) {
    fprintf(stderr, "[ERROR]: could not open directory \"%s\"\n",
            d->option->directory);
    exit(-1);
  }

  do_search(d);
  d->status = 0;
  closedir(dp);
  return (void *)NULL;
}

// ok
void display_status(search_t *search) {
  char *rollingwheel[] = {
      ".  ", ".  ", ".  ", ".  ", "   ", "   ", "   ", "   ", "   ", "   ",
      "   ", "   ", "   ", "   ", "   ", "   ", "   ", "   ", "   ", "   ",
      " . ", " . ", " . ", " . ", "   ", "   ", "   ", "   ", "   ", "   ",
      "   ", "   ", "   ", "   ", "   ", "   ", "   ", "   ", "   ", "   ",
      "  .", "  .", "  .", "  .", "   ", "   ", "   ", "   ", "   ", "   ",
      "   ", "   ", "   ", "   ", "   ", "   ", "   ", "   ", "   ", "   ",
  };
  static int i = 0;
  attron(COLOR_PAIR(COLOR_FILE));
  if (search->status)
    mvaddstr(0, COLS - 3, rollingwheel[++i % 60]);
  else
    mvaddstr(0, COLS - 5, "");
}

// ok
static void open_entry(search_t *search, int index, const char *editor,
                       const char *pattern) {
  int i;
  entry_t *ptr;
  entry_t *file = search->result->start;
  char command[PATH_MAX];
  pthread_mutex_t *mutex;

  for (i = 0; ptr = search->result->start, i < index; i++) {
    ptr = ptr->next;
    gt_file_t *cast_result = (gt_file_t *)get_type(ptr, FILE_ENTRY);
    if (cast_result)
      file = &cast_result->entry;
  }

  line_t *line = container_of(ptr, line_t, entry);

  LOCK(search->data_mutex) {
    snprintf(command, sizeof(command), editor, pattern, line->line, file->data);
  }

  if (system(command) < 0)
    return;

  line->opened = 1;
}

// ok
int main(int argc, char *argv[]) {

  configuration_t *config = create_config();
  option_t *option = create_option(config, argc, argv);
  search_t *search = create_search(option);
  g_search = search;

  pthread_mutex_init(&search->data_mutex, NULL);

  display_t *display;
  display = creare_display();
  g_display = display;
  g_config = config;
  // =============================================

  signal(SIGINT, sig_handler);
  if (pthread_create(&pid, NULL, lookup_thread, search)) {
    fprintf(stderr, "%s: can not create thread\n", argv[0]);
    free_search(search);
    exit(-1);
  }

  pthread_mutex_t *mutex;
  LOCK(search->data_mutex) display_results(display, search, LINES);

  int ch;
  while ((ch = getch())) {
    switch (ch) {
    case KEY_RESIZE:
      LOCK(search->data_mutex)
      resize_display(display, search, LINES);
      break;
    case CURSOR_DOWN:
    case KEY_DOWN:
      LOCK(search->data_mutex)
      move_cursor_down_and_refresh(display, search);
      break;
    case CURSOR_UP:
    case KEY_UP:
      LOCK(search->data_mutex)
      move_cursor_up_and_refresh(display, search);
      break;
    case KEY_PPAGE:
    case PAGE_UP:
    case CTRL_U:
      LOCK(search->data_mutex)
      move_page_up_and_refresh(display, search);
      break;
    case KEY_NPAGE:
    case PAGE_DOWN:
    case CTRL_D:
      LOCK(search->data_mutex)
      move_page_down_and_refresh(display, search);
      break;
    case ENTER:
    case '\n':
      if (search->result->nbentry == 0)
        break;
      stop_ncurses(display);
      open_entry(search, display->cursor + display->index,
                 search->option->editor, search->option->pattern);
      start_ncurses(display, config);
      resize_display(display, search, LINES);
      break;
    case QUIT:
      goto quit;
    default:
      break;
    }

    if (search->status == 0)
      nodelay(stdscr, false);
    else
      usleep(10000);

    LOCK(search->data_mutex) {
      display_results(display, search, LINES);
      display_status(search);

      if (search->result->nbentry != 0 && display->ncurses_initialized) {
        start_ncurses(display, config);
        display->ncurses_initialized = 1;
      }
    }

    LOCK(search->data_mutex) {
      if (search->status == 0 && search->result->nbentry == 0)
        goto quit;
    }
  }

// as label
quit:
  pthread_cancel(pid);
  pthread_join(pid, NULL);
  stop_ncurses(display);
  free_search(search);
  destroy_config(config);

  return 0;
}
